package cn.bonoon.kernel.bytecode.asm;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
import org.springframework.context.ApplicationContext;

import cn.bonoon.entities.ConfigEntity;
import cn.bonoon.kernel.annotations.Keyword;
import cn.bonoon.kernel.annotations.ObjectClass;
import cn.bonoon.kernel.annotations.Unique;
import cn.bonoon.kernel.bytecode.ConfigConverter;
import cn.bonoon.kernel.bytecode.ConfigHandler;
import cn.bonoon.kernel.descriptors.ConfigDescriptor;
import cn.bonoon.kernel.descriptors.ConfigPropertyAccessor;
import cn.bonoon.kernel.events.BaseEvent;

/**
 * <pre>
 * 负责生成这样一个对象：
 * 1.把一个普通的实体转换成配置格式的数据进行存储
 * 2.把配置格式的数据转换成一个普通的配置实体
 * </pre>
 * @author jackson
 *
 */
public class ASMConfigBuilder extends ASMAbstractBuilder<ConfigConverter, ConfigDescriptor>{
	
	public ASMConfigBuilder(ApplicationContext applicationContext, ConfigDescriptor configDescriptor){
		super(applicationContext, ConfigConverter.class, ConfigEntity.class, configDescriptor);
	}
	
	@Override
	protected void internalBuild() throws Exception {
		Constructor<ConfigHandler> handlerInit = ConfigHandler.class.getConstructor(EntityManager.class, BaseEvent.class, String.class);
		String chdes = Type.getConstructorDescriptor(handlerInit);
		int 
			__this = 0,
			__manager = 1,
			__event = 2,
			__config = 3,
			__handler = 4,
			__list = 5,
			__map = 5, 
			__value = 6;
		
		String fdes = "Ljava/lang/String;", fn = "configDomain";
		constructor.visitVarInsn(ALOAD, __this);
		constructor.visitLdcInsn(objectDescriptor.md5());
		constructor.visitFieldInsn(PUTFIELD, classPath, fn, fdes);
		
		MethodVisitor 
			setter = _visitMethod("set", EntityManager.class, BaseEvent.class),
			getter = _visitMethod("get", EntityManager.class, BaseEvent.class);
		String eventPath = Type.getInternalName(BaseEvent.class);
		String handlerPath = Type.getInternalName(ConfigHandler.class);
		
		setter.visitVarInsn(ALOAD, __event);
		setter.visitMethodInsn(INVOKEVIRTUAL, eventPath, "getSource", "()Ljava/lang/Object;");
		setter.visitTypeInsn(CHECKCAST, objectPath);
		setter.visitVarInsn(ASTORE, __config);
		setter.visitTypeInsn(NEW, handlerPath);  
		setter.visitInsn(DUP);
		setter.visitVarInsn(ALOAD, __manager);
		setter.visitVarInsn(ALOAD, __event);
		setter.visitVarInsn(ALOAD, __this);
		setter.visitFieldInsn(GETFIELD, classPath, fn, fdes);
		setter.visitMethodInsn(INVOKESPECIAL, handlerPath, "<init>", chdes);
		setter.visitVarInsn(ASTORE, __handler);
		
		setter.visitVarInsn(ALOAD, __handler);
		setter.visitMethodInsn(INVOKEVIRTUAL, handlerPath, "loadEntities", "()Ljava/util/List;");
		setter.visitVarInsn(ASTORE, __list);
		
		Label ifnull = new Label();
		getter.visitVarInsn(ALOAD, __event);
		getter.visitMethodInsn(INVOKEVIRTUAL, eventPath, "getSource", "()Ljava/lang/Object;");
		getter.visitTypeInsn(CHECKCAST, objectPath);
		getter.visitVarInsn(ASTORE, __config);
		getter.visitVarInsn(ALOAD, __config);
		getter.visitJumpInsn(IFNONNULL, ifnull);
		getter.visitTypeInsn(NEW, objectPath);  
		getter.visitInsn(DUP);
		getter.visitMethodInsn(INVOKESPECIAL, objectPath, "<init>", "()V");
		getter.visitVarInsn(ASTORE, __config);
		getter.visitLabel(ifnull);
		getter.visitTypeInsn(NEW, handlerPath);  
		getter.visitInsn(DUP);
		getter.visitVarInsn(ALOAD, __manager);
		getter.visitVarInsn(ALOAD, __event);
		getter.visitVarInsn(ALOAD, __this);
		getter.visitFieldInsn(GETFIELD, classPath, fn, fdes);
		getter.visitMethodInsn(INVOKESPECIAL, handlerPath, "<init>", chdes);
		getter.visitVarInsn(ASTORE, __handler);
		
		getter.visitVarInsn(ALOAD, __handler);
		getter.visitMethodInsn(INVOKEVIRTUAL, handlerPath, "loadMap", "()Ljava/util/Map;");
		getter.visitVarInsn(ASTORE, __map);
		
		String saveDes = getDes("save", ConfigHandler.class, List.class, String.class, String.class);
		
		//set 0-this,1-EntityManager,2-oid,4-uid,6-Object,7-Class,8-List,9-item
		//get 0-this,1-EntityManager,2-oid,4-uid,6-Class,7-Map,8-item
		for(ConfigPropertyAccessor cpa : objectDescriptor.values()){
			Method gm = cpa.getter(), sm = cpa.setter();
			Class<?> ct = cpa.getType();
			setter.visitVarInsn(ALOAD, __config);
			setter.visitMethodInsn(INVOKEVIRTUAL, objectPath, gm.getName(), Type.getMethodDescriptor(gm));

			getter.visitVarInsn(ALOAD, __map);
			getter.visitLdcInsn(cpa.getName());
			getter.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
			getter.visitTypeInsn(CHECKCAST, "java/lang/String");
			getter.visitVarInsn(ASTORE, __value);
			//判断是否为null
			if(ct.isPrimitive()){
				ifnull = new Label();
				getter.visitVarInsn(ALOAD, __value);
				getter.visitJumpInsn(IFNULL, ifnull);
				
				new StringDescriptor(ct).visitMethodInsn(setter);
				getter.visitVarInsn(ALOAD, __config);
				getter.visitVarInsn(ALOAD, __value);
				if(int.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I");
				}else if(long.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "parseLong", "(Ljava/lang/String;)J");
				}else if(double.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "parseDouble", "(Ljava/lang/String;)D");
				}else if(float.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "parseFloat", "(Ljava/lang/String;)F");
				}else if(boolean.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "parseBoolean", "(Ljava/lang/String;)Z");
				}else if(short.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "parseShort", "(Ljava/lang/String;)S");
				}else if(byte.class.equals(ct)){
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "parseByte", "(Ljava/lang/String;)B");
				}else{//char
					getter.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I");
					getter.visitInsn(I2C);
				}
				getter.visitMethodInsn(INVOKEVIRTUAL, objectPath, sm.getName(), Type.getMethodDescriptor(sm));
				getter.visitLabel(ifnull);
			}else if(Date.class.equals(ct)){
				setter.visitMethodInsn(INVOKESTATIC, "cn/bonoon/kernel/util/StringHelper", "datetime2String", "(Ljava/util/Date;)Ljava/lang/String;");
				getter.visitVarInsn(ALOAD, __config);
				getter.visitVarInsn(ALOAD, __value);
				getter.visitMethodInsn(INVOKESTATIC, "cn/bonoon/kernel/util/StringHelper", "toDatetime", "(Ljava/lang/String;)Ljava/util/Date;");
				getter.visitMethodInsn(INVOKEVIRTUAL, objectPath, sm.getName(), Type.getMethodDescriptor(sm));
			}else if(!String.class.equals(ct)){
				setter.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(ct), "toString", "()Ljava/lang/String;");
				try{
					Method vof = ct.getDeclaredMethod("valueOf", String.class);
					String des = Type.getInternalName(ct);
					getter.visitVarInsn(ALOAD, __config);
					getter.visitVarInsn(ALOAD, __value);
					getter.visitMethodInsn(INVOKESTATIC, des, "valueOf", Type.getMethodDescriptor(vof));
					getter.visitMethodInsn(INVOKEVIRTUAL, objectPath, sm.getName(), Type.getMethodDescriptor(sm));
				}catch(Exception ex){
					if(Character.class.equals(ct)){
						getter.visitVarInsn(ALOAD, __config);
						getter.visitVarInsn(ALOAD, __value);
						getter.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I");
						getter.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
						getter.visitMethodInsn(INVOKEVIRTUAL, objectPath, sm.getName(), Type.getMethodDescriptor(sm));
					}else{
						ObjectClass oc = cpa.getAnnotation(ObjectClass.class);
						Class<?> tryClass = null == oc ? ct : oc.value();
						try{
							Constructor<?> con = tryClass.getConstructor(String.class);
							String tcDes = Type.getInternalName(tryClass);
							getter.visitVarInsn(ALOAD, __config);
							getter.visitTypeInsn(NEW, tcDes);  
							getter.visitInsn(DUP);
							getter.visitVarInsn(ALOAD, __value);
							getter.visitMethodInsn(INVOKESPECIAL, tcDes, "<init>", Type.getConstructorDescriptor(con));
							getter.visitMethodInsn(INVOKEVIRTUAL, objectPath, sm.getName(), Type.getMethodDescriptor(sm));
						}catch(Exception t){ /*nothing to do!*/ }
					}
				}
			}else{
				getter.visitVarInsn(ALOAD, __config);
				getter.visitVarInsn(ALOAD, __value);
				getter.visitMethodInsn(INVOKEVIRTUAL, objectPath, sm.getName(), Type.getMethodDescriptor(sm));
			}
			setter.visitVarInsn(ASTORE, __value);
			//在存储的时候进行是否唯一值进行判断
			Unique unique = cpa.getAnnotation(Unique.class);
			if(null != unique){
				setter.visitVarInsn(ALOAD, __this);
				setter.visitVarInsn(ALOAD, __handler);
				setter.visitLdcInsn(cpa.getName());
				setter.visitVarInsn(ALOAD, __value);
				setter.visitLdcInsn(unique.value());
				visitMethodInsn(setter, "uniqueCheck", ConfigHandler.class, String.class, String.class, String.class);
			}
			Keyword keyword = cpa.getAnnotation(Keyword.class);
			if(null != keyword){
				KeywordChecker kChecker = new KeywordChecker(keyword, this);
				if(kChecker.checkArray()){
					setter.visitVarInsn(ALOAD, __value);
					kChecker.checkArray(setter);
				}
				if(kChecker.checkDB()){
					setter.visitVarInsn(ALOAD, __manager);
					setter.visitVarInsn(ALOAD, __value);
					kChecker.checkDB(setter);
				}
			}
			setter.visitVarInsn(ALOAD, __this);
			setter.visitVarInsn(ALOAD, __handler);
			setter.visitVarInsn(ALOAD, __list);
			setter.visitLdcInsn(cpa.getName());
			setter.visitVarInsn(ALOAD, __value);
			setter.visitMethodInsn(INVOKEVIRTUAL, classPath, "save", saveDes);
		}
		setter.visitVarInsn(ALOAD, __this);
		setter.visitVarInsn(ALOAD, __manager);
		setter.visitVarInsn(ALOAD, __list);
		visitMethodInsn(setter, "remove", EntityManager.class, List.class);
		_return(setter, RETURN);
		
		getter.visitVarInsn(ALOAD, __config);
		_return(getter, ARETURN);
	}
	
	private void visitMethodInsn(MethodVisitor visitor, String name, Class<?>... pts) throws Exception{
		visitor.visitMethodInsn(INVOKEVIRTUAL, classPath, name, getDes(name, pts));
	}
	
	private String getDes(String name, Class<?>... parameterTypes)throws Exception{
		return Type.getMethodDescriptor(parentClass.getDeclaredMethod(name, parameterTypes));
	}
}
